import logging
from pathlib import Path

import numpy as np

from app.core.config import BILBAO_DATA

# 设置日志
logger = logging.getLogger(__name__)

var = "??"


# 变量占位符
def check_input(inputfile: Path):
    """检查输入文件中的变量"""

    with inputfile.open("r") as f:
        num_vars = 0
        (nsg, numk, nband) = tuple(map(int, f.readline().strip().split()))

        k_withvar = []
        for i in range(numk):
            data = f.readline().strip().split()
            for d in data:
                if d == var:
                    num_vars += 1
                    k_withvar.append(int(data[0]))
        return nsg, num_vars, k_withvar


# ----------------------- Compatibility relations -----------------------


def check_pair(inputfile: Path, pairfile: Path):
    """检查输入文件与配对文件的兼容性关系

    Args:
        inputfile: 输入文件路径
        pairfile: 配对文件路径

    Returns:
        不匹配的K点列表，空列表表示全部匹配
    """
    with pairfile.open("r") as f:
        num_hspk = int(f.readline())
        pair = {}
        for ik in range(num_hspk):
            tmp = f.readline().split()
            kind = int(tmp[2]) + 1
            kname = tmp[3]
            num_irr = int(tmp[4])
            if (kind, kname) not in pair:
                # 忽略配对文件中的'UA'
                pair[(kind, kname)] = {}
            for ir in range(num_irr):
                tmp = list(map(int, f.readline().strip().split()))
                pair[(kind, kname)][tmp[0] + 1] = (tmp[1], tmp[2] + 1, tmp[3] + 1)

    def check_pair_func(irrlist: list[int], irrpair: dict[int, tuple[int, int, int]]) -> bool:
        """检查不可约表示列表与配对关系的匹配

        Args:
            irrlist: 不可约表示列表
            irrpair: 配对关系字典

        Returns:
            是否匹配
        """
        match = True
        for ir in irrpair:
            if irrpair[ir][0] == -1:
                num_ir = irrlist.count(ir)
                if num_ir % 2 > 0:
                    match = False
                    return match
            elif irrpair[ir][0] == 0:
                ir1 = irrpair[ir][1]
                ir2 = irrpair[ir][2]
                num_ir1 = irrlist.count(ir1)
                num_ir2 = irrlist.count(ir2)
                if num_ir1 != num_ir2:
                    match = False
                    return match
        return match

    with inputfile.open("r") as f:
        sg, numk, _ = list(map(int, f.readline().strip().split()))
        k_notmatch = []
        for ik in range(numk):
            tmp = list(map(int, f.readline().strip().split()))
            indk = tmp[0]
            match = True
            if indk > 0:
                for kinfo in pair:
                    if kinfo[0] == indk:
                        match = check_pair_func(tmp[1:], pair[kinfo])
                        break
            else:
                kinfo = None
                for k in pair:
                    if k[0] == -indk:
                        kinfo = k
                        break
                if kinfo:
                    for kinfo2 in pair:
                        if kinfo2[1] == kinfo[1] + "A":
                            match = check_pair_func(tmp[1:], pair[kinfo2])
                            break
            if not match:
                k_notmatch.append(ik)

    return k_notmatch


def run_CR(inputfile: str | Path, soc: int, nmsg: int | None = None) -> tuple[list[str], str]:
    """执行兼容性关系(CR)检查

    该函数检查给定输入文件中的能带表示是否满足兼容性关系，
    可选择性地检查磁性空间群(MSG)下的兼容性关系。

    Args:
        inputfile: 输入文件路径，通常是tqc.data
        soc: 是否有自旋轨道耦合(SOC) (0/1)
        nmsg: OG磁性空间群编号 (可选)

    Returns:
        crossline: 不满足兼容性关系的对称线列表（在nmsg不为None时，返回不匹配的k点列表）
        msg: 兼容性关系检查结果信息
            - "Satisfy CR": 满足所有兼容性关系
            - "CR is not satisfied on the following line(s)": 不满足的对称线列表
            - "The following k-points do not match the CR relations under MSGs": 不匹配的k点列表


    Raises:
        ValueError: 如果输入文件中有未定义的表示
        FileNotFoundError: 如果所需文件不存在
    """
    # 检查输入文件中是否有变量（问号）
    inputfile = Path(inputfile)
    nsg, num_vars, _ = check_input(inputfile)
    if num_vars > 0:
        raise ValueError("There are undefined representations in the inputfile.")

    # 确定CR文件路径
    crfile = BILBAO_DATA / "CompRel" / f"{nsg}_{'soc' if soc else 'nsoc'}.txt"

    # 如果提供了磁性空间群编号，进行MSG相关检查
    if nmsg:
        # 读取MSG信息
        msginfo = {}
        msginfo_path = BILBAO_DATA / "msginfo"

        with msginfo_path.open("r") as f:
            fdata = f.readlines()

        for i in range(1651):
            tmp = fdata[i].strip().split()
            msginfo[int(tmp[0])] = tmp[2]

        # 确定pairfile路径
        if soc:
            pairfile = BILBAO_DATA / "pairfiles_msghspk" / f"{msginfo[nmsg]}.txt"
        else:
            pairfile = BILBAO_DATA / "pairfiles_msghspk_nsoc" / f"{msginfo[nmsg]}.txt"

        # 检查配对关系
        k_notmatch = check_pair(inputfile, pairfile)
        if k_notmatch:
            msg = "The following k-points do not match the CR relations under MSGs:"
            logger.warning(f"{msg} {k_notmatch}")
            return k_notmatch, msg

    # 读取CR数据
    symlines = []
    kirrep = []

    with crfile.open("r") as f:
        num_row, num_col = list(map(int, f.readline().strip().split()))
        crmat = np.zeros((num_row, num_col), dtype=int)

        # 读取对称线信息
        for ir in range(num_row):
            symlines.append(f.readline().strip())

        # 读取CR矩阵
        for ir in range(num_row):
            tmp = list(map(int, f.readline().strip().split()))
            for ic in range(num_col):
                crmat[ir, ic] = tmp[ic]

        # 读取k点不可约表示信息
        tmp = f.readline()
        for ic in range(num_col):
            kirrep.append(tmp[5 * ic : 5 * (ic + 1) + 1].strip())

    # 处理k点不可约表示名称
    for ic in range(num_col):
        if soc:
            kirrep[ic] = kirrep[ic][:-1]
        kirrep[ic] = kirrep[ic].replace("m", "")

    # 提取k点名称
    tmp = []
    kvecname = []
    for ic in range(num_col):
        for istr in range(len(kirrep[ic])):
            if kirrep[ic][istr].isdigit():
                tmp.append(kirrep[ic][:istr])
                break
    for ic in range(num_col):
        if tmp[ic] not in kvecname:
            kvecname.append(tmp[ic])

    # 读取k点字典
    kdict = {}
    kvec_list_path = BILBAO_DATA / "CompRel/kvec_list_230msg.txt"

    with kvec_list_path.open("r") as f:
        lines = f.readlines()
        iline = 0
        num_lines = len(lines)

        while iline < num_lines:
            sg_in_k, numk = list(map(int, lines[iline].strip().split()))
            if sg_in_k == nsg:
                iline += 1
                for i in range(numk):
                    tmp = lines[iline + i].split()
                    kind = int(tmp[0])
                    if kind not in kdict:
                        kdict[kind] = {}
                        kdict[kind]["num_irr"] = int(tmp[1])
                        kdict[kind]["num_irr_single"] = int(tmp[2])
                        kdict[kind]["num_irr_double"] = int(tmp[3])
                        kdict[kind]["name"] = tmp[5]
                break
            else:
                iline += numk + 1

    # 读取输入文件的k点和不可约表示
    maxk = []
    irre_onk = []

    with inputfile.open("r") as f:
        (nsg_in, nmaxk, nband) = tuple(map(int, f.readline().strip().split()))
        for i in range(nmaxk):
            data = list(map(int, f.readline().strip().split()))
            maxk.append(data[0])
            irre_onk.append(data[1:])

    # 构建能带向量
    bandvec = np.zeros((num_col, 1), dtype=int)
    icol = 0
    for kname in kvecname:
        # 找到对应的k点
        kind = None
        for k in kdict:
            if kdict[k]["name"] == kname:
                kind = k
                break

        if kind is None:
            logger.warning(f"找不到k点名称 {kname} 对应的k点")
            continue

        # 找到对应的输入文件中的k点索引
        ik = None
        for i in range(len(maxk)):
            if maxk[i] == kind:
                ik = i
                break

        if ik is None:
            logger.warning(f"输入文件中找不到k点 {kind}")
            continue

        # 确定可能的不可约表示
        if soc:
            possible_irr = [ir + 1 for ir in range(kdict[kind]["num_irr_single"], kdict[kind]["num_irr"])]
        else:
            possible_irr = [ir + 1 for ir in range(kdict[kind]["num_irr_single"])]

        # 累加能带向量
        for ir in possible_irr:
            for ir_on_k in irre_onk[ik]:
                if ir_on_k == ir:
                    bandvec[icol] += 1
            icol += 1

    # 检查兼容性关系
    crossline = []
    checkvec = np.dot(crmat, bandvec)
    for ic in range(len(checkvec)):
        if checkvec[ic] != 0:
            crossline.append(symlines[ic])

    if not crossline:
        msg = "Satisfy CR"
    else:
        msg = "CR is not satisfied on the following line(s):"
        logger.warning(f"{msg}: {crossline}")

    # 返回不满足CR的对称线列表
    return crossline, msg


def calc_ind(inputfile: str | Path, soc: int, nmsg: int):
    """计算自旋指标
    该函数计算给定输入文件中的自旋指标，并返回结果。
    这里nmsg是必须的参数，表示OG磁性空间群编号。
    """
    inputfile = Path(inputfile)
    nsg, num_vars, _ = check_input(inputfile)
    if num_vars > 0:
        raise ValueError("There are undefined representations in the inputfile.")

    msginfo: dict[int, str] = {}
    msginfo_path = BILBAO_DATA / "msginfo"

    lines = msginfo_path.read_text().splitlines()

    for i in range(1651):
        tmp = lines[i].strip().split()
        if len(tmp) >= 3:
            msginfo[int(tmp[0])] = tmp[2]

    if soc:
        indfile = BILBAO_DATA / "output" / f"Lindex_{msginfo[nmsg]}.txt"
    else:
        indfile = BILBAO_DATA / "output_nsoc" / f"Lindex_{msginfo[nmsg]}.txt"

    with indfile.open("r") as f:
        num_ind, num_kirr = list(map(int, f.readline().strip().split()))

        if num_ind == 0:
            return "No symmetry indicator."

        ind_group = list(map(int, f.readline().strip().split()))
        f.readline()
        ind_formula = []
        for i in range(num_ind):
            ind_formula.append(list(map(int, f.readline().strip().split())))

        irr_onk = [{"kind": 0, "irrname": "", "irrind": [], "A": False} for ik in range(num_kirr)]
        for ik in range(num_kirr):
            tmp = f.readline().strip().split()
            irrname_tmp = tmp[-1]
            irrname_tmp = irrname_tmp.replace("KA", "K")
            irrname_tmp = irrname_tmp.replace("HA", "H")
            irrname_tmp = irrname_tmp.replace("WA", "W")
            irrname_tmp = irrname_tmp.replace("PA", "P")
            origk_ind = int(tmp[2])
            for jk in range(num_kirr):
                if irrname_tmp == irr_onk[jk]["irrname"]:
                    irr_onk[ik]["A"] = True
                    origk_ind = irr_onk[jk]["kind"]
                    break
            irr_onk[ik]["kind"] = origk_ind
            irr_onk[ik]["irrname"] = irrname_tmp
            if int(tmp[1]) == 1:
                irr_onk[ik]["irrind"].append(int(tmp[3]))
            else:
                irr_onk[ik]["irrind"].append(int(tmp[3]))
                irr_onk[ik]["irrind"].append(int(tmp[5]))

    # The label of KA is a confusion in the whole project.
    #   KA induced by high symmetry k-points and time-reversal symmetry (used in type II MSGs)
    # are written explicitly in kvec_list files, and in the Lindex files,
    # their index are positive and bigger than other K index.
    # In irvsp, KA are given as minus index.
    #   KA induded by high symmetry line k-points and antiunitary symmetry (used in type III MSGs)
    # are written explicitly in kvec_list_msginsub.txt, with the same index as K.
    # In the Lindex files, KA and K share the same index and names. The order of k-points in Lindex files
    # are same as the order in kvec_list_msginsub.txt.
    # In irvsp, such KA have the same index as K.

    irrdata = dict()
    with inputfile.open("r") as f:
        sg, numk, _ = list(map(int, f.readline().strip().split()))
        for ik in range(numk):
            tmp = list(map(int, f.readline().strip().split()))
            if not irrdata.__contains__(tmp[0]):
                irrdata[tmp[0]] = tmp[1:]
            else:
                irrdata[-tmp[0]] = tmp[1:]

    # print(irr_onk)
    # print(irrdata)

    count = [0 for irk in range(len(irr_onk))]
    for irk in range(len(irr_onk)):
        kind = irr_onk[irk]["kind"]
        if irr_onk[irk]["A"]:
            kind = -kind
        irrind = irr_onk[irk]["irrind"]
        if len(irrind) == 1:
            count[irk] = irrdata[kind].count(irrind[0])
        else:
            irrind1 = irrind[0]
            irrind2 = irrind[1]
            if irrind1 == irrind2:
                num_tmp = irrdata[kind].count(irrind1)
                assert num_tmp % 2 == 0
                count[irk] = int(num_tmp / 2)
            else:
                num_tmp1 = irrdata[kind].count(irrind1)
                num_tmp2 = irrdata[kind].count(irrind2)
                assert num_tmp1 == num_tmp2
                count[irk] = num_tmp1

    valZ = []
    for ind, n in enumerate(ind_group):
        formula = ind_formula[ind]
        Z = 0
        for irk in range(len(formula)):
            Z += count[irk] * formula[irk]
        valZ.append(Z % n)

    Zstr = ""
    for ind, n in enumerate(ind_group):
        Zstr += "Z" + str(n) + "=" + str(valZ[ind]) + ","

    return Zstr
